package org.jing.ext.stream;

import org.jing.core.lang.JingException;
import org.jing.core.lang.JingExtraException;
import org.jing.core.util.StringUtil;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * Description: <br>
 *
 * @author longxiandahun <br>
 * @since 2022-08-05 <br>
 */
public class StreamUtil {
    /**
     * Description: 采用超时方式连接的socket <br>
     *
     * @param ip IP地址 <br>
     * @param port 端口 <br>
     * @param timeout 超时 <br>
     * @return 连接完成的Socket实例 <br>
     */
    public static Socket connect(String ip, int port, int timeout) {
        try {
            Socket socket = new Socket();
            socket.connect(new InetSocketAddress(ip, port), timeout);
            socket.setSoTimeout(timeout);
            return socket;
        }
        catch (IOException e) {
            throw new JingException(e, "failed to connect to socket");
        }
    }

    /**
     * Description: 按照指定编码格式从输入流读取字符串 <br>
     * 先读取6个长度的字节数组作为后续读取长度依据, 然后根据长度读取作为实际字符串 <br>
     *
     * @param inputStream 输入流 <br>
     * @param encoding 字符集, 缺省utf-8 <br>
     * @return <br>
     */
    public static String readStream(InputStream inputStream, String encoding) throws JingExtraException {
        try {
            // 报文头长度
            byte[] buffer = new byte[6];
            if (6 != inputStream.read(buffer)) {
                throw new JingExtraException("E000", "invalid packet length header: {}", new String(buffer));
            }
            int length = StringUtil.parseInteger(new String(buffer)) - 4;
            // 报文头响应码
            buffer = new byte[4];
            if (4 != inputStream.read(buffer)) {
                throw new JingExtraException("E000", "invalid packet code: {}", new String(buffer));
            }
            String code = new String(buffer);
            // 缓冲区大小
            int bufferSize = Math.min(3 * 1024, length);
            // 剩余长度
            int restSize = length;
            // 每次读取长度
            int index;
            buffer = new byte[length];
            StringBuilder stbr = new StringBuilder();
            while (restSize > 0 && -1 != (index = inputStream.read(buffer, 0, Math.min(restSize, bufferSize)))) {
                stbr.append(new String(buffer, 0, index, StringUtil.ifEmpty(encoding, "utf-8")));
                restSize -= index;
            }
            String request = stbr.toString();
            stbr.setLength(0);
            if (!"0000".equals(code)) {
                throw new JingExtraException(code, request);
            }
            else {
                return request;
            }
        }
        catch (JingException e) {
            throw e;
        }
        catch (Throwable t) {
            throw new JingExtraException(t, "9999", "failed to read stream");
        }
    }

    /**
     * Description: 按照缺省编码格式从输入流读取字符串 <br>
     * 先读取6个长度的字节数组作为后续读取长度依据, 然后根据长度读取作为实际字符串 <br>
     *
     * @param inputStream 输入流 <br>
     * @return <br>
     */
    public static String readStream(InputStream inputStream) throws JingExtraException {
        return readStream(inputStream, null);
    }

    /**
     * Description: 按照指定编码格式向输出流写入字符串 <br>
     * 先写入4个长度的字节数组作为本次消息的成功与否标识, 再写入6个长度的字节数组作为后续写入长度依据, 然后再写入实际字符串 <br>
     *
     * @param outputStream 输出流 <br>
     * @param code 结果码 0000成功 <br>
     * @param content 字符串 <br>
     * @param encoding 字符集, 缺省utf-8 <br>
     */
    public static void writeStream(OutputStream outputStream, String code, String content, String encoding) throws JingExtraException {
        try {
            code = StringUtil.ifEmpty(code, "0000");
            code = StringUtil.cutStringByMaxBytesLength(code, "utf-8", 4);
            content = String.format("%s%s", code, content);
            byte[] buffer = content.getBytes(StandardCharsets.UTF_8);
            int length = buffer.length;
            outputStream.write(String.format("%06d", length).getBytes(StringUtil.ifEmpty(encoding, "utf-8")));
            outputStream.write(buffer);
            outputStream.flush();
        }
        catch (JingException e) {
            throw e;
        }
        catch (Throwable t) {
            throw new JingExtraException(t, "9999", "failed to write stream");
        }
    }

    /**
     * Description: 按照缺省编码格式向输出流写入字符串 <br>
     *
     * @param outputStream 输出流 <br>
     * @param code 结果码 0000成功 <br>
     * @param content 字符串 <br>
     */
    public static void writeStream(OutputStream outputStream, String code, String content) throws JingExtraException {
        writeStream(outputStream, code, content, null);
    }

    /**
     * Description: 按照缺省编码格式向输出流写入字符串 <br>
     * 使用结果码为0000 <br>
     *
     * @param outputStream 输出流 <br>
     * @param content 字符串 <br>
     */
    public static void writeStream(OutputStream outputStream, String content) throws JingExtraException {
        writeStream(outputStream, null, content, null);
    }

    /**
     * Description: 向输出流写入布尔值 <br>
     * 只会写入一个字节, 1代表true, 0代表false <br>
     *
     * @param outputStream 输出流 <br>
     * @param flag 布尔值 <br>
     */
    public static void writeBoolean(OutputStream outputStream, boolean flag) throws JingExtraException {
        try {
            outputStream.write(new byte[] { (byte) (flag ? 1 : 0) });
            outputStream.flush();
        }
        catch (JingException e) {
            throw e;
        }
        catch (Throwable t) {
            throw new JingExtraException(t, "9999", "failed to write stream");
        }
    }

    /**
     * Description: 从输入流读取布尔值 <br>
     * 只会读取一个字节, 1代表true, 0代表false <br>
     *
     * @param inputStream 输入流 <br>
     * @return <br>
     */
    public static boolean readBoolean(InputStream inputStream) throws JingExtraException {
        try {
            byte[] buffer = new byte[1];
            if (-1 == inputStream.read(buffer)) {
                return false;
            }
            return buffer[0] == 1;
        }
        catch (JingException e) {
            throw e;
        }
        catch (Throwable t) {
            throw new JingExtraException(t, "9999", "failed to read stream");
        }
    }

    public static byte[] getBuffer(InputStream is) {
        try (
            ByteArrayOutputStream bos = new ByteArrayOutputStream()
        ) {
            byte[] buffer = new byte[1024];
            int length;
            while (-1 != (length = is.read(buffer))) {
                bos.write(buffer, 0, length);
            }
            return bos.toByteArray();
        }
        catch (IOException e) {
            throw new JingException(e);
        }
    }
}
